#include <stx/imp/cmn.hpp>

inline namespace stx
{
UniqueId getUniqueId()
{
    static auto ls = std::atomic_uint64_t{};
    return UniqueId(++ls);
}

Number& Number::operator++()
{
    if (this->isInt64()) [[likely]]
    {
        return *this = this->getInt64() + 1;
    }
    else if (this->isUint64()) [[likely]]
    {
        return *this = this->getUint64() + 1;
    }
    else [[unlikely]]
    {
        return *this = this->getLongDouble() + 1;
    }
}

Number Number::operator++(int)
{
    auto const old = *this;
    ++*this;
    return old;
}

Number& Number::operator--()
{
    if (this->isInt64()) [[likely]]
    {
        return *this = this->getInt64() - 1;
    }
    else if (this->isUint64()) [[likely]]
    {
        return *this = this->getUint64() - 1;
    }
    else [[unlikely]]
    {
        return *this = this->getLongDouble() - 1;
    }
}

Number Number::operator--(int)
{
    auto const old = *this;
    --*this;
    return old;
}

Number& Number::operator+=(Number const n)
{
    return *this = *this + n;
}

Number& Number::operator-=(Number const n)
{
    return *this = *this - n;
}

Number& Number::operator*=(Number const n)
{
    return *this = *this * n;
}

Number& Number::operator/=(Number const n)
{
    return *this = *this / n;
}

Number& Number::operator%=(Number const n)
{
    return *this = *this % n;
}

std::optional<Number> ton(std::string_view const sv, NumberBase const base)
{
    try
    {
        if (sv.contains('.')) [[unlikely]]
        {
            auto buf = std::array<char, 24>{};
            std::memcpy(buf.data(), sv.data(), sv.size());

            auto end = Ptr<char>{};
            return Number(std::strtold(buf.data(), &end));
        }

        auto n = std::int64_t{};
        auto const [uu, ec] =
            std::from_chars(sv.data(), sv.data() + sv.size(), n, ton(base));
        if (std::errc{} != ec)
        {
            LOG_ERROR(sv);
            return {};
        }

        return Number(n);
    }
    catch (std::exception const& e)
    {
        LOG_ERROR(e.what(), _I_, sv, _I_, base);
    }
    catch (...)
    {
        LOG_ERROR(sv, _I_, base);
    }

    return {};
}

std::optional<Number> ton(std::string_view const sv)
{
    return ton(sv, NumberBase::K_10);
}

std::size_t hex2bin(std::string_view const sv_hex,
                    void* const            obuf,
                    std::size_t const      obuf_size)
{
    if (sv_hex.size() % 2)
    {
        RUNTIME_ERROR("Invalid hex string size: "sv, sv_hex.size());
    }

    auto       output = std::bit_cast<std::byte*>(obuf);
    auto const olen   = sv_hex.size() / 2;

    if (obuf_size < olen)
    {
        RUNTIME_ERROR("Output buffer is too small: "sv, obuf_size, ""sv, olen);
    }

    LOOP (i, olen)
    {
        auto const hc = static_cast<std::uint8_t>(sv_hex[i * 2]);
        auto       hb = std::uint8_t{};

        if (EXPR(hc >= '0') && EXPR(hc <= '9'))
        {
            hb = ((hc - '0') << 4_u8) & 0xf0_u8;
        }
        else if (EXPR(hc >= 'a') && EXPR(hc <= 'f'))
        {
            hb = ((hc - 'a' + 10) << 4_u8) & 0xf0_u8;
        }
        else if (EXPR(hc >= 'A') && EXPR(hc <= 'F'))
        {
            hb = ((hc - 'A' + 10) << 4_u8) & 0xf0_u8;
        }
        else
        {
            throw std::runtime_error("Invalid hex string (1): "s.append(
                sv_hex.data(), sv_hex.size()));
        }

        auto const lc = static_cast<std::uint8_t>(sv_hex[i * 2 + 1]);
        auto       lb = std::uint8_t{};

        if (EXPR(lc >= '0') && EXPR(lc <= '9'))
        {
            lb = lc - '0';
        }
        else if (EXPR(lc >= 'a') && EXPR(lc <= 'f'))
        {
            lb = lc - 'a' + 10;
        }
        else if (EXPR(lc >= 'A') && EXPR(lc <= 'F'))
        {
            lb = lc - 'A' + 10;
        }
        else
        {
            throw std::runtime_error("Invalid hex string (2): "s.append(
                sv_hex.data(), sv_hex.size()));
        }

        output[i] = static_cast<std::byte>(hb | lb);
    }

    return olen;
}

Buffer hex2bin(std::string_view const sv_hex)
{
    auto ret = Buffer(sv_hex.size() / 2);
    hex2bin(sv_hex, ret.data(), ret.size());
    return ret;
}

std::string bin2hex(void const* const buf,
                    std::size_t const size,
                    SbLowercase const sb_lowercase)
{
    if (!buf || 0 == size)
    {
        return {};
    }

    auto const p   = std::bit_cast<std::byte const*>(buf);
    auto       ret = std::string{};
    resizeDefaultInit(ret, size * 2);

    LOOP (idx, size)
    {
        ret[idx * 2]     = getHighHalfHexChar(p[idx], sb_lowercase);
        ret[idx * 2 + 1] = getLowHalfHexChar(p[idx], sb_lowercase);
    }

    return ret;
}

[[nodiscard]] std::string bin2hex(void const* const buf, std::size_t const size)
{
    return bin2hex(buf, size, SbLowercase{});
}
} // namespace stx
